/**
 * Copyright (C) 2008 aileron.cc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cc.aileron.commons.accessor.properties;

import static cc.aileron.commons.accessor.properties.PojoNumberConvertUtils.convertNumber;
import static cc.aileron.commons.exception.NoSuchAccessorRuntimeException.MethodType.GET;
import static cc.aileron.commons.exception.NoSuchAccessorRuntimeException.MethodType.SET;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;

import cc.aileron.commons.accessor.GetterDxo;
import cc.aileron.commons.accessor.PojoAccessorConvertor;
import cc.aileron.commons.accessor.PojoAccessorConvertorFactory;
import cc.aileron.commons.accessor.SetterDxo;
import cc.aileron.commons.accessor.factory.PojoAccessorInstanceManager;
import cc.aileron.commons.exception.NoSuchAccessorRuntimeException;

/**
 * PojoProperties 実態
 * @author Aileron
 * @param <T> 
 *
 */
public class PojoPropertiesImpl <T> implements PojoProperties <T>
{
    /*
     * (非 Javadoc)
     * @see cc.aileron.commons.accessor.PojoProperties#newPropertieInstance(java.lang.String)
     */
    public Object newPropertieInstance(final String name)
    {
        final String lowerName = name.toLowerCase();
        if(field.containsKey(lowerName))
        {
            try
            {
                final Class<?> fieldClass = field.get(lowerName).getType();
                final Object object = fieldClass.newInstance();
                setObject(name, object);
                return object;
            }
            catch (Exception e)
            {
                throw new RuntimeException(e);
            }
        }
        return null;
    }
    
    /* (非 Javadoc)
     * @see cc.aileron.commons.accessor.PojoProperties#getObject(java.lang.String)
     */
    public Object getObject(String name) throws NoSuchAccessorRuntimeException
    {
        if ("self".equals(name))
        {
            return this.target;
        }
        try
        {
            return getPropertie(name);
        }
        catch (NoSuchMethodException e)
        {
        }
        try
        {
            return getField(name);
        }
        catch (NoSuchFieldException e)
        {
            throw new NoSuchAccessorRuntimeException( GET, target, name, null );
        }
    }

    /* (非 Javadoc)
     * @see cc.aileron.commons.accessor.PojoProperties#setObject(java.lang.String, java.lang.Object)
     */
    public void setObject(String name, Object value)
            throws NoSuchAccessorRuntimeException
    {
        try
        {
            if (setPropertie(name, value))
            {
                return ;
            }
        }
        catch (NoSuchMethodException e)
        {
        }
        
        try
        {
            setField(name, value);
        }
        catch (NoSuchFieldException e)
        {
            throw new NoSuchAccessorRuntimeException( SET, target, name , value );
        }
    }

    /*
     * (非 Javadoc)
     * @see cc.aileron.commons.accessor.PojoProperties#toTarget()
     */
    public T toTarget()
    {
        return target;
    }    
    
    @Override
    public String toString()
    {
        return this.getClass().getName() + "$" + this.target;
    }
    
    /**
     * プロパティの取得
     * 
     * @param name
     * @return
     * @throws NoSuchMethodException 
     */
    private Object getPropertie(final String name) throws NoSuchMethodException
    {
        final String lowerPropertieName = name.toLowerCase();
        for(final String methodName : methods)
        {
            final Method m = method.get(methodName+lowerPropertieName);
            if(m!=null)
            {
                try
                {
                    final PojoAccessorConvertor<Object,Object> convertor = getterDxo(m.getAnnotation(GetterDxo.class));
                    final Object result = m.invoke(target); 
                    if(convertor==null)
                    {
                        return result;
                    }
                }
                catch (Throwable e)
                {
                }
            }
        }
        throw new NoSuchMethodException(name);
    }
    private static final String[] methods = {"get","is","to"};

    /**
     * フィールドの取得
     * 
     * @param name
     * @return
     * @throws NoSuchFieldException 
     */
    private Object getField(final String name) throws NoSuchFieldException
    {
        try
        {
            final Field f = field.get(name.toLowerCase());
            if (f == null)
            {
                throw new NoSuchFieldException(name);    
            }
            final PojoAccessorConvertor<Object,Object> convertor = getterDxo(f.getAnnotation(GetterDxo.class));
            final Object result = f.get(target);
            if(convertor==null)
            {
                return result;
            }
            return convertor.convert(result);
        }
        catch (IllegalArgumentException e)
        {
        }
        catch (IllegalAccessException e)
        {
        }
        return null;
    }
    
    /**
     * プロパティの設定
     * 
     * @param name
     * @return
     * @throws NoSuchMethodException 
     */
    private boolean setPropertie(final String name, final Object object) throws NoSuchMethodException
    {
        final String methodName = "set" + name.toLowerCase() ;
        final Method m = method.get(methodName);
        if(m==null)
        {
            throw new NoSuchMethodException(name);
        }
        final PojoAccessorConvertor<Object,Object> convertor = setterDxo(m.getAnnotation(SetterDxo.class));
        try
        {
            if(convertor!=null)
            {
                m.invoke(target, convertor.convert(object));
                return true;
            }
            
            m.invoke(target, object);
            return true;
        }
        catch (IllegalArgumentException e)
        {
            if(object==null && m.getParameterTypes()[0]==Boolean.TYPE)
            {
                try
                {
                    m.invoke(target,false);
                    return true;
                }
                catch (Throwable ex)
                {
                }
            }
            if(object==null && m.getParameterTypes()[0].isPrimitive())
            {
                try
                {
                    m.invoke(target,0);
                    return true;
                }
                catch (Throwable ex)
                {
                }
            }         
            if(object==null)
            {
                return false;
            }
            if(Number.class.isAssignableFrom(object.getClass()))
            {
                try
                {
                    final Class<?> type = m.getParameterTypes()[0];
                    final Number number = (Number) object;
                    m.invoke(target, convertNumber(type,number) );
                    return true;
                }
                catch (Throwable ex)
                {
                }
            }
        }
        catch (Exception e)
        {
        }
        return false;
    }

    /**
     * フィールドの設定
     * 
     * @param name
     * @return
     * @throws NoSuchFieldException 
     */
    private boolean setField(final String name, final Object object) throws NoSuchFieldException
    {
        final Field f = field.get(name.toLowerCase());
        if(f==null)
        {
            throw new NoSuchFieldException(name);
        }
        final PojoAccessorConvertor<Object,Object> convertor = setterDxo(f.getAnnotation(SetterDxo.class));
        try
        {
            if(convertor!=null)
            {
                f.set(target, convertor.convert(object));
                return true;
            }
            f.set(target, object);
            return true;
        }
        catch (IllegalArgumentException e)
        {
            final Class<?> type = f.getType();
            if(object==null && type == Boolean.TYPE)
            {
                try
                {
                    f.set(target,false);
                    return true;
                }
                catch (Throwable ex)
                {
                }
            }
            if(object==null && type.isPrimitive())
            {
                try
                {
                    f.set(target,0);
                    return true;
                }
                catch (Throwable ex)
                {
                }
            }
            if(object==null)
            {
                return false;
            }
            if(Enum.class.isAssignableFrom(type))
            {
                try
                {
                    final Number number = (Number) object;
                    f.set(target,type.getMethod("valueOf", Integer.TYPE).invoke(null, number.intValue()));
                    return true;
                }
                catch (Throwable ex)
                {
                    return false;
                }
            }
            if(String.class.equals(object.getClass()))
            {
                try
                {
                    final Number number = Integer.valueOf( (String)object );
                    f.set(target, convertNumber(type,number));
                    return true;
                }
                catch(Exception ex)
                {
                    return false;
                }
            }
            if(Number.class.isAssignableFrom(object.getClass()))
            {
                try
                {
                    final Number number = (Number) object;
                    f.set(target, convertNumber(type,number));
                    return true;
                }
                catch(Exception ex)
                {
                    return false;
                }
            }
        }
        catch (IllegalAccessException e)
        {
        }
        return false;
    }
    

    /**
     * constractor set object
     * @param instanceManager 
     * @param target 
     */
    public PojoPropertiesImpl(
            final PojoAccessorInstanceManager instanceManager,
            final T target)
    {
        this.convertorFactory = instanceManager.getConvertorFactory();
        this.target = target;
        for (final Field value : target.getClass().getFields())
        {
            final String key = value.getName();
            final String lowerKey = key.toLowerCase();
            field.put(lowerKey, value);
        }
        for (final Method value : target.getClass().getMethods())
        {
            final String key = value.getName();
            final String lowerKey = key.toLowerCase();
            method.put(lowerKey, value);
        }
    }
    
    /**
     * getter に付与されているDxoを取得
     * @param dxo
     * @return
     */
    private PojoAccessorConvertor<Object,Object> getterDxo(final GetterDxo dxo)
    {
        if(dxo==null)
        {
            return null;
        }
        return convertorFactory.getInstance(dxo.value());
    }
    
    /**
     * setter に付与されているDxoを取得
     * @param dxo
     * @return
     */
    private PojoAccessorConvertor<Object,Object> setterDxo(final SetterDxo dxo)
    {
        if(dxo==null)
        {
            return null;
        }
        return convertorFactory.getInstance(dxo.value());
    }
    
    /**
     * instance-manager
     */
    private final PojoAccessorConvertorFactory convertorFactory ;
    
    /**
     * target
     */
    private final T target ;

    /**
     * フィールド Map
     */
    private final HashMap<String, Field> field = new HashMap<String, Field>();

    /**
     * メソッド Map
     */
    private final HashMap<String, Method> method = new HashMap<String, Method>();
}